Skip to content

Version-controlled hooks: skillhook.yaml in a repository, link/unlink, run: shell hooks - #6

Merged
JOsacky merged 1 commit into
mainfrom
claude/skillhook-version-control-38a533
Sep 16, 2026
Merged

JOsacky merged 1 commit into
mainfrom
claude/skillhook-version-control-38a533

Conversation

@JOsacky

@JOsacky JOsacky commented Sep 16, 2026

Copy link
Copy Markdown
Member

What

A repository can now declare its webhooks in a skillhook.yaml at its root, version-controlled with the code they act on, so "which skill runs from which webhook" has one reviewable answer. Each hook maps a webhook name to what runs:

hooks:
  pull-after-merge:                       # POST /hooks/pull-after-merge
    run: git pull --ff-only               # a shell command, run in the repository with the payload on stdin
    auth: { type: github, secret_env: GITHUB_WEBHOOK_SECRET }
    when:
      - { header: x-github-event, equals: pull_request }
      - { path: action, equals: closed }
      - { path: pull_request.merged, equals: true }
  release-notes:
    skill: .claude/skills/release-notes   # a SKILL.md directory in the repository, served under the hook's name
    model: sonnet
  summarize:
    prompt: Summarize the payload into {{job_dir}}/summary.md.   # inline instructions for the agent
  • Exactly one of run: / skill: / prompt: per hook, plus any field of the skillhook: block (auth, when, model, cwd, env, timeout_seconds, …). cwd defaults to the repository; the default secret is SKILLHOOK_SECRET_<HOOK>. Secrets are named in the file, never stored there.
  • CLI: skillhook link [dir] registers a repository (new projects key in skillhook.json), unlink <dir> removes it, projects lists linked repositories with hooks and URLs, projects init [dir] writes a starter file and links it. MCP: list_projects, link_project (with init), unlink_project.
  • Live reload: the registry re-reads projects, every skillhook.yaml and every referenced SKILL.md on change. link and git pull need no restart.
  • Precedence: ~/.skillhook/skills first, then linked repositories in order; a duplicate name is reported by skills list, skills validate, doctor and the server log instead of being served.
  • Visibility: skills list gained a source column, skills show a source: line, GET /skills and the MCP skill tools a source field, doctor a project <dir> check.
  • schema/skillhook.yaml.schema.json is generated alongside the config schema (editor completion via the yaml-language-server comment on the starter file's first line).
  • This repository now carries its own skillhook.yaml (a pull-after-merge hook), the dogfood example of the new docs/projects.md.

Design notes

  • A compiled hook is an ordinary Skill (source.type === "project"), so the server, queue, runners, prompt builder and job store are untouched. The registry moved to src/registry.ts (it needs src/projects.ts, which needs the schemas in src/skills.ts; keeping it in skills.ts would have been an import cycle). HookSchema extends SkillhookBlockSchema, so new block fields reach hooks automatically.
  • run: is sugar for runner: shell + shell.command; the existing shell runner is reused unchanged.

Security

  • src/server.ts only gains file and source in the skill summary (admin route). No change to auth.ts, runners/env.ts or prompt.ts.
  • Trust model: linking a repository means trusting its skillhook.yaml exactly as one trusts a SKILL.md in ~/.skillhook/skills; only the machine owner can link (config file on disk / admin CLI / MCP), never a webhook. run: commands are argv or /bin/sh -c <literal string from the file>; payload data never reaches a command line (stdin and $SKILLHOOK_PAYLOAD_PATH only), and the docs say so.
  • Secrets never enter the repository: hooks name secret_env, values stay in .env (mode 600); link generates skillhook-managed secrets exactly like skills new.

Tests

npm run check is green (124 tests). New: src/projects.test.ts (schema, compilation, path resolution, starter template), src/registry.test.ts (merge order, shadowing, mtime reloads of manifest / SKILL.md / config), an HTTP test that serves a run: hook and a skill: hook from a linked repository end to end, and a CLI test for projects init → link → projects → skills list/show → run → doctor → unlink. CI smoke tests now link this repository's own skillhook.yaml and exercise projects init with the installed tarball.

🤖 Generated with Claude Code

…link`, `run:` shell hooks

A repository can now declare its webhooks in a skillhook.yaml at its root, checked in
with the code they act on. Each hook maps a webhook name to what runs: `run:` (a shell
command executed in the repository with the payload on stdin), `skill:` (a SKILL.md
directory in the repository, served under the hook's name) or `prompt:` (inline
instructions for the agent), plus any field of the `skillhook:` block. Secrets are
named in the file and stored per machine in .env.

- `skillhook link [dir]` registers a repository in the new `projects` key of
  skillhook.json; `unlink` removes it; `projects` lists linked repositories with their
  hooks and URLs; `projects init [dir]` writes a starter file and links it. MCP:
  list_projects, link_project (with init), unlink_project.
- SkillRegistry (now src/registry.ts) serves <home>/skills first, then linked projects
  in config order, re-reading `projects`, every skillhook.yaml and every referenced
  SKILL.md when they change, so nothing needs a restart. Duplicate names are reported
  by skills list/validate, doctor and the server log instead of being served.
- A compiled hook is an ordinary Skill with `source.type === "project"`; skills list,
  skills show, GET /skills, the MCP skill tools and doctor show where a skill comes from.
- schema/skillhook.yaml.schema.json is generated next to the config schema, and this
  repository carries its own skillhook.yaml with a pull-after-merge hook.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@JOsacky
JOsacky merged commit 9930f19 into main Sep 16, 2026
6 checks passed
@JOsacky
JOsacky deleted the claude/skillhook-version-control-38a533 branch September 16, 2026 22:07
@JOsacky JOsacky mentioned this pull request Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant